using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;

namespace Fadd.Plugins
{
    /// <summary>
    /// General purpose plugin finder class.
    /// </summary>
    /// <remarks>
    /// This class scans a directory after all classes that contains the specified types.
    /// All assembly checking is done in a separate app domain, which means that no dlls are loaded
    /// during the process.
    /// </remarks>
    [Serializable]
    public class PluginFinder
    {
        private List<PluginTypeInfo> _plugins;

        /// <summary>
        /// Initializes a new instance of the <see cref="PluginFinder"/> class.
        /// </summary>
        public PluginFinder()
        {
            _plugins = new List<PluginTypeInfo>();
        }

        private void Assign(List<PluginTypeInfo> plugins)
        {
            _plugins = plugins;
        }

        /// <summary>
        /// All found plugins
        /// </summary>
        public List<PluginTypeInfo> Plugins
        {
            get { return _plugins; }
        }

        /// <summary>
        /// Finds the specified full path.
        /// </summary>
        /// <param name="fullPath">Path and wildcards.</param>
        /// <param name="types">Types wanted.</param>
        /// <example>
        /// <code>
        /// List&gt;Type&lt; types = new List&gt;Type&lt;();
        /// types.Add(typeof(IPlayer));
        /// types.Add(typeof(IViewer));
        /// finder.Find("C:\\myapp\\plugins\\plugin*.dll", types);
        /// </code>
        /// </example>
        /// <exception cref="ReflectionTypeLoadException"></exception>
		/// <exception cref="TypeLoadException"></exception>
        public void Find(string fullPath, IEnumerable<Type> types)
        {
            AppDomain domain = AppDomain.CreateDomain("ModuleLoader");
			
			PluginFinder finder = (PluginFinder)domain.CreateInstanceFromAndUnwrap(
													GetType().Assembly.Location ?? string.Empty, GetType().Namespace + ".PluginFinder");

            finder.FindInternal(fullPath, types);
            Assign(finder.Plugins);

            AppDomain.Unload(domain);            
        }

        /// <summary>
        /// Finds the specified full path.
        /// </summary>
        /// <param name="fullPath">Path and wildcards.</param>
        /// <param name="types">Types wanted.</param>
        /// <example>
        /// <code>
        /// List&gt;Type&lt; types = new List&gt;Type&lt;();
        /// types.Add(typeof(IPlayer));
        /// types.Add(typeof(IViewer));
        /// finder.Find("C:\\myapp\\plugins\\plugin*.dll", types);
        /// </code>
        /// </example>
        /// <exception cref="TypeLoadException"></exception>
        protected void FindInternal(string fullPath, IEnumerable<Type> types)
        {
            string searchString;
            string directory;
            if (fullPath.Contains("."))
            {
                int pos = fullPath.LastIndexOf('\\');
                if (pos == -1)
                {
                    searchString = fullPath;
                    directory = Environment.CurrentDirectory;
                }
                else
                {
                    searchString = fullPath.Substring(pos + 1);
                    directory = fullPath.Substring(0, pos);
                }
            }
            else
            {
                searchString = "*.dll";
                directory = fullPath;
            }

            foreach (string fileName in Directory.GetFiles(directory, searchString))
            {
            	try
            	{
					Assembly asm;
            	    byte[] publicKey = null;
					try
					{
						asm = Assembly.LoadFrom(fileName);
					    AssemblyName assemblyName = asm.GetName();
                        publicKey = assemblyName.GetPublicKey();
					}
					catch (FileLoadException err)
					{
						Console.WriteLine(err);
						continue;
					}


					PluginTypeInfo plugin = null;
					foreach (Type wantedType in types)
					{

						Type[] assemblyTypes;
						try
						{
							assemblyTypes = asm.GetTypes();
						}
						catch(ReflectionTypeLoadException err)
						{
							Console.WriteLine(err);
							continue;
						}
						

                        foreach (Type type in assemblyTypes)
                        {
                            if (wantedType.IsAssignableFrom(type) && !type.IsInterface)
                                Add(ref plugin, asm, type, publicKey);
                        }
                        /*
						if (wantedType.IsInterface)
						{
							foreach (Type type in asm.GetTypes())
							{
                                type.GetInterfaceMap()
								if (type.GetInterface(wantedType.Name) != null)
									Add(ref plugin, asm, type, publicKey);
							}
						}
						else if (wantedType.IsClass)
						{
							foreach (Type type in asm.GetTypes())
							{
								if (type.IsSubclassOf(wantedType))
                                    Add(ref plugin, asm, type, publicKey);
							}
						}
                         **/
					}
            	}
            	catch (ReflectionTypeLoadException e)
            	{
            		throw new TypeLoadException("Couldn't load plugin: " + fileName, e);
            	}
            }
        }


        /// <summary>
        /// Adds the specified plugin.
        /// </summary>
        /// <param name="plugin">The plugin.</param>
        /// <param name="assembly">Assembly that the plugin resides in.</param>
        /// <param name="type">Plugin interface type.</param>
        /// <param name="publicKey">Public key, should be used to decide the amount of access for the module.</param>
        protected void Add(ref PluginTypeInfo plugin, Assembly assembly, Type type, byte[] publicKey)
        {
            if (plugin == null)
            {
                plugin = new PluginTypeInfo(assembly.Location, type, publicKey);
                _plugins.Add(plugin);
            }
            else
                plugin.Add(type);
        }

    }
}